<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>自定义指令</title>
  </head>
  <body>
    <ul>
      <li v-for="(item,index) in arr" :key="index" v-my-click="item">
        {{item}}
      </li>
    </ul>
  </body>
  <script>
    function callHook$1(dir, hook, vnode, oldVnode, isDestroy) {
      var fn = dir.def && dir.def[hook];
      if (fn) {
        try {
          fn(vnode.elm, dir, vnode, oldVnode, isDestroy);
        } catch (e) {
          handleError(
            e,
            vnode.context,
            "directive " + dir.name + " " + hook + " hook"
          );
        }
      }
    }

    function _update(oldVnode, vnode) {
      //判断旧节点是不是空节点，是的话表示新建/初始化组件
      var isCreate = oldVnode === emptyNode;
      //判断新节点是不是空节点，是的话表示销毁组件
      var isDestroy = vnode === emptyNode;
      //获取旧节点上的所有自定义指令
      var oldDirs = normalizeDirectives$1(
        oldVnode.data.directives,
        oldVnode.context
      );
      //获取新节点上的所有自定义指令
      var newDirs = normalizeDirectives$1(vnode.data.directives, vnode.context);

      //保存inserted钩子函数
      var dirsWithInsert = [];
      //保存componentUpdated钩子函数
      var dirsWithPostpatch = [];

      var key, oldDir, dir;

      //这里先说下callHook$1函数的作用
      //callHook$1有五个参数，第一个参数是指令对象，第二个参数是钩子函数名称，第三个参数新节点，
      //第四个参数是旧节点，第五个参数是是否为注销组件，默认为undefined，只在组件注销时使用
      //在这个函数里，会根据我们传递的钩子函数名称，运行我们自定义组件时，所声明的钩子函数，

      //遍历所有新节点上的自定义指令
      for (key in newDirs) {
        oldDir = oldDirs[key];
        dir = newDirs[key];
        //如果旧节点中没有对应的指令，一般都是初始化的时候运行
        if (!oldDir) {
          //对该节点执行指令的bind钩子函数
          callHook$1(dir, "bind", vnode, oldVnode);
          //dir.def是我们所定义的指令的五个钩子函数的集合
          //如果我们的指令中存在inserted钩子函数
          if (dir.def && dir.def.inserted) {
            //把该指令存入dirsWithInsert中
            dirsWithInsert.push(dir);
          }
        } else {
          //如果旧节点中有对应的指令，一般都是组件更新的时候运行
          //那么这里进行更新操作，运行update钩子（如果有的话）
          //将旧值保存下来，供其他地方使用（仅在 update 和 componentUpdated 钩子中可用）
          dir.oldValue = oldDir.value;
          //对该节点执行指令的update钩子函数
          callHook$1(dir, "update", vnode, oldVnode);
          //dir.def是我们所定义的指令的五个钩子函数的集合
          //如果我们的指令中存在componentUpdated钩子函数
          if (dir.def && dir.def.componentUpdated) {
            //把该指令存入dirsWithPostpatch中
            dirsWithPostpatch.push(dir);
          }
        }
      }

      //我们先来简单讲下mergeVNodeHook的作用
      //mergeVNodeHook有三个参数，第一个参数是vnode节点，第二个参数是key值，第三个参数是回函数
      //mergeVNodeHook会先用一个函数wrappedHook重新封装回调，在这个函数里运行回调函数
      //如果该节点没有这个key属性，会新增一个key属性，值为一个数组，数组中包含上面说的函数wrappedHook
      //如果该节点有这个key属性，会把函数wrappedHook追加到数组中

      //如果dirsWithInsert的长度不为0，也就是在初始化的时候，且至少有一个指令中有inserted钩子函数
      if (dirsWithInsert.length) {
        //封装回调函数
        var callInsert = function () {
          //遍历所有指令的inserted钩子
          for (var i = 0; i < dirsWithInsert.length; i++) {
            //对节点执行指令的inserted钩子函数
            callHook$1(dirsWithInsert[i], "inserted", vnode, oldVnode);
          }
        };
        if (isCreate) {
          //如果是新建/初始化组件，使用mergeVNodeHook绑定insert属性，等待后面调用。
          mergeVNodeHook(vnode, "insert", callInsert);
        } else {
          //如果是更新组件，直接调用函数，遍历inserted钩子
          callInsert();
        }
      }

      //如果dirsWithPostpatch的长度不为0，也就是在组件更新的时候，且至少有一个指令中有componentUpdated钩子函数
      if (dirsWithPostpatch.length) {
        //使用mergeVNodeHook绑定postpatch属性，等待后面子组建全部更新完成调用。
        mergeVNodeHook(vnode, "postpatch", function () {
          for (var i = 0; i < dirsWithPostpatch.length; i++) {
            //对节点执行指令的componentUpdated钩子函数
            callHook$1(
              dirsWithPostpatch[i],
              "componentUpdated",
              vnode,
              oldVnode
            );
          }
        });
      }

      //如果不是新建/初始化组件，也就是说是更新组件
      if (!isCreate) {
        //遍历旧节点中的指令
        for (key in oldDirs) {
          //如果新节点中没有这个指令（旧节点中有，新节点没有）
          if (!newDirs[key]) {
            //从旧节点中解绑，isDestroy表示组件是不是注销了
            //对旧节点执行指令的unbind钩子函数
            callHook$1(oldDirs[key], "unbind", oldVnode, oldVnode, isDestroy);
          }
        }
      }
    }
  </script>
</html>
